<!--
 * @Author: Wangtao
 * @Date: 2020-09-21 09:55:09
 * @LastEditors: zhanwang
 * @LastEditTime: 2021-11-18 17:42:06
-->
<template>
  <div class="workflow" ref="workflow" v-loading="loading" element-loading-text="初始化数据...">
    <div class="drag-box border" id="drag">
      <node-menu @addNode="addNode" ref="nodeMenu"></node-menu>
      <canvas-operation
        v-if="!isEmpty(ivrData)"
        @pageBack="pageBack"
        @scale="zoomScale"
        @enlarge="enlargeCanvas"
        @save="ivrSaveClick"
        :data="ivrData"
      ></canvas-operation>
      <!-- 主要画布 -->
      <section
        @dragover="onDragOver"
        class="drop test"
        @contextmenu="canvasRightClick"
        ref="mainContainer"
        @keyup.delete="keyBoardClick"
      >
        <div id="canvas" class="canvas" ref="efContainer" :style="canvasStyle">
          <flow-node
            v-for="node in data.nodeList"
            :key="node.id"
            :id="node.id"
            :node="node"
            :activeElement="activeElement"
            @changeNodeSite="changeNodeSite"
            @dbClick="dbClickNode"
            @nodeClick="nodeClick"
            @delete="deleteNode"
          >
          </flow-node>
        </div>
      </section>
    </div>
    <!-- 右侧节点配置 -->
    <resize-drawer
      :key="currentNode.type"
      :title="nameMap[currentNode.type]"
      v-model="isShowDrawer"
      :append-to-body="true"
      direction="rtl"
      ref="drawer"
      :size="currentNode.type === 'ivr-node-dynamic-route' ? 700 : 576"
      @on-close-draw="onCancel"
    >
      <template #column>
        <component
          v-if="isShowDrawer"
          :key="currentNode.id"
          :is="currentNode.type"
          :class="currentNode.id"
          :ref="currentNode.id"
          :data="data"
          :currentNode="currentNode"
          :save="nodeEditSave"
          :cancel="nodeEditCancel"
          :delete="deleteNode"
        ></component>
      </template>
    </resize-drawer>
  </div>
</template>

<script lang="ts">
/* eslint-disable @typescript-eslint/no-unused-vars */
import {
  getCurrentInstance,
  onBeforeMount,
  onMounted,
  nextTick,
  toRefs,
  reactive,
  defineComponent,
  onBeforeUnmount,
  markRaw
} from 'vue'
import { jsPlumb } from 'jsplumb'
import panzoom from 'panzoom'
import canvasOperation from './canvas-operation.vue'
import nodeMenu from './node-menu.vue'
import config from './config'
import FlowNode from './node.vue'
import { merge, cloneDeep, isEmpty } from 'lodash'
import { v4 as uuidv4 } from 'uuid'
// 节点配置
import ivrNodeStart from '../node-edit/start-node.vue'
import ivrNodeDate from '../node-edit/ivr-node-date.vue'
import ivrNodeMenu from '../node-edit/ivr-node-menu.vue'
import ivrNodePlay from '../node-edit/ivr-node-play.vue'
import ivrNodeTransfer from '../node-edit/ivr-node-transfer.vue'
import ivrNodeVoicemail from '../node-edit/ivr-node-voicemail.vue'
import ivrNodeCallurl from '../node-edit/ivr-node-callurl.vue'
import ivrNodeCollect from '../node-edit/ivr-node-collect.vue'
import ivrNodeDynamicRoute from '../node-edit/ivr-node-dynamic-route.vue'
import ivr from '@/app/call-center/store/modules/ivr'
import { IvrData, LineListObj, NodeConditionObj, NodeDataObj, NodeObj } from '../types/index'
// 节点默认配置
import defaultNode from './node-default'
import { ElMessage, ElMessageBox } from 'element-plus'
import localforage from 'localforage'
import { NODE_STORE } from '../constants'
import ResizeDrawer from '@/common/components/resize-drawer/index.vue'
import mcGetCache from '@/common/utils/mc-get-cache'
import deleteIcon from '@/common/components/delete-icon/index.vue'
const IvrModule: any = ivr()

export default defineComponent({
  name: 'flowChart',
  components: {
    canvasOperation,
    nodeMenu,
    FlowNode,
    ivrNodeStart,
    ivrNodeDate,
    ivrNodeMenu,
    ivrNodePlay,
    ivrNodeTransfer,
    ivrNodeVoicemail,
    ivrNodeCallurl,
    ivrNodeCollect,
    ivrNodeDynamicRoute,
    ResizeDrawer
  },
  emits: ['pageBack', 'ivrSaveSuccess'],
  setup(props: any, { emit }) {
    const title = '编辑'
    const nameMap: any = {
      'ivr-node-start': `${title}开始节点`,
      'ivr-node-play': `${title}播放语音节点`,
      'ivr-node-date': `${title}时间条件节点`,
      'ivr-node-menu': `${title}按键菜单节点`,
      'ivr-node-transfer': `${title}转人工节点`,
      'ivr-node-voicemail': `${title}留言节点`,
      'ivr-node-callurl': `${title}数据交互节点`,
      'ivr-node-collect': `${title}采集收号节点`,
      'ivr-node-dynamic-route': `${title}分支路由节点`
    }
    const { jsplumbSetting, jsplumbConnectOptions, jsplumbSourceOptions, jsplumbTargetOptions } = config
    // const props: any = withDefaults(
    //   defineProps<{
    //     ivrData: any
    //   }>(),
    //   {
    //     ivrData: {}
    //   }
    // )

    interface State {
      savedNodeList: string[]
      loading: boolean
      data: IvrData
      nodeForm: any
      activeElement: any
      dragInstance: any
      dropInstance: any
      nodes: any
      currentNode: any
      deleteTarget: any
      isShowDrawer: any // 右侧抽屉
      contextMenuPosition: any
      zoom: any
      mainContainerWrap: any
      pan: any
      flowInfo: any
      jsPlumb: any
      canvasStyle: any
      getTransform: any
      easyFlowVisible: any
      loadEasyFlowFinish: boolean
      ivrData: any
    }

    const state: State = reactive({
      ivrData: {},
      savedNodeList: [],
      loading: false,
      data: {
        nodeList: []
      },
      nodeForm: {
        // 右侧抽屉form
        nodeId: '',
        nodeName: '',
        desc: ''
      },
      activeElement: {
        type: '',
        sourceId: '',
        targetId: ''
      },
      dragInstance: '',
      dropInstance: '', // jsplumb 在右侧画布的实例,
      nodes: [],
      currentNode: {},
      deleteTarget: {
        type: '',
        nodeId: '', // 删除节点需要
        connection: '' // 删除连线需要
      },
      isShowDrawer: false, // 右侧抽屉,
      contextMenuPosition: [-100, -100],
      zoom: 1,
      mainContainerWrap: '',
      pan: '',
      flowInfo: {},
      jsPlumb: null,
      canvasStyle: {
        width: '',
        height: '',
        left: '',
        top: '',
        transformOrigin: '0px 0px 0px'
      },
      getTransform: {},
      easyFlowVisible: false,
      loadEasyFlowFinish: false
    })

    const _this: any = getCurrentInstance()

    // const drawerRefs: any = ref([])

    // const workflow: any = ref(null)

    // const efContainer: any = ref(null)

    // const mainContainer: any = ref(null)

    // onBeforeUpdate(() => {
    //   drawerRefs.value = []
    // })

    const setCanvasSize = (isMount?: boolean) => {
      const elFlow = window.getComputedStyle(_this.refs.workflow)
      const width = parseFloat(elFlow.width.replace('px', ''))
      const height = parseFloat(elFlow.height.replace('px', ''))
      // 画布大小
      const setWidth = 40000
      const setHeight = 20000
      const setLeft = (setWidth / 2 - width / 2) * -1
      const setTop = (setHeight / 2 - height / 2) * -1
      state.canvasStyle = {
        width: setWidth + 'px',
        height: setHeight + 'px',
        left: (isMount ? setLeft - 120 : setLeft) + 'px',
        top: (isMount ? setTop - 83 : setTop) + 'px',
        transformOrigin: '0px 0px 0px'
      }
    }

    const zoomCanvas = () => {
      const mainContainer = _this.refs.efContainer
      const mainContainerWrap = _this.refs.mainContainer
      const el = window.getComputedStyle(mainContainerWrap)
      const sWidth = (40000 / parseFloat(el.width.replace('px', ''))) * 0.5
      const sHeight = (20000 / parseFloat(el.height.replace('px', ''))) * 0.5
      const pan = panzoom(mainContainer, {
        smoothScroll: false,
        bounds: false,
        zoomDoubleClickSpeed: 1,
        minZoom: 0.3,
        maxZoom: 1,
        zoomSpeed: 0.1,
        initialX: 0,
        initialY: 0,
        transformOrigin: { x: sWidth, y: sHeight }
      })
      state.mainContainerWrap = mainContainerWrap
      state.pan = pan
      // 缩放时设置jsPlumb的缩放比率
      pan.on('zoomend', (e: any) => {
        const { scale } = e.getTransform()
        state.getTransform = e.getTransform()
        state.jsPlumb.setZoom(scale)
      })
    }

    const pageBack = () => {
      emit('pageBack')
    }

    onBeforeMount(() => {
      if (!IvrModule.cacheSign) {
        state.loading = true
        IvrModule.getNodeCache().then(() => {
          state.loading = false
        })
      }
    })

    const getLocalData = async () => {
      return await localforage.getItem(NODE_STORE)
    }

    onMounted(async () => {
      const localData: any = await getLocalData()
      state.ivrData = localData
      setCanvasSize(true)
      await nextTick()
      // 默认加载流程的数据、在这里可以根据具体的业务返回符合流程数据格式的数据即可
      const elFlow = window.getComputedStyle(_this.refs.efContainer)
      const left: number = parseFloat(elFlow.width.replace('px', '')) / 2 - 80
      const top: number = parseFloat(elFlow.height.replace('px', '')) / 2 - 100
      let getDataB: IvrData | undefined
      if (localData.nodeList?.length) {
        getDataB = localData
      } else {
        getDataB = {
          name: '',
          nodeList: [
            {
              id: 'startNode',
              type: 'ivr-node-start',
              left: left + 'px',
              top: top + 'px',
              viewOnly: true,
              condition: [{ label: '下一步' }],
              data: getDefaultNode('ivr-node-start')
            }
          ],
          lineList: []
        }
      }
      dataReload(getDataB)
    })

    /**
     * @description 获取缩放比率
     * @returns {number} 缩放比率
     */
    const getScale = () => {
      let scale1
      if (state.pan) {
        const { scale } = state.pan.getTransform()
        scale1 = scale
      } else {
        const matrix = window.getComputedStyle(state.dropInstance.getContainer()).transform
        scale1 = (matrix as any).split(', ')[3] * 1
      }
      state.dropInstance.setZoom(scale1)
      return scale1
    }

    /**
     * @description: 屏蔽浏览器右键点击事件
     * @param {type}
     * @return:
     */
    const canvasRightClick = (event: any) => {
      event.preventDefault()
      event.stopPropagation()
      return false
    }

    const zoomScale = (type: any) => {
      const mainContainer = _this.refs.efContainer
      const x = mainContainer.clientWidth / 2
      const y = mainContainer.clientHeight / 2
      if (type === 'out') {
        state.pan.smoothZoom(x, y, 1.5)
      } else if (type === 'in') {
        state.pan.smoothZoom(x, y, 0.5)
      } else {
        state.pan.smoothZoom(0, 0, 100)
        state.pan.moveTo(0, 0)
      }
    }

    // 平移缩放操作
    const enlargeCanvas = async (eLarge: any) => {
      const position = eLarge ? 'fixed' : 'absolute'
      _this.refs.workflow.parentNode.style.position = position
      await nextTick()
      setTimeout(() => {
        setCanvasSize()
        zoomCanvas()
      })
    }

    const addNode = async (evt: any, Menu: any) => {
      const screenX = evt.originalEvent.clientX
      const screenY = evt.originalEvent.clientY
      const matrix = window.getComputedStyle(_this.refs.efContainer).transform
      const scale1 = (matrix as any).split(', ')[3] * 1
      const containerRect = _this.refs.efContainer.getBoundingClientRect()
      let left = screenX
      let top = screenY
      // 计算是否拖入到容器中
      if (
        left < containerRect.x ||
        left > containerRect.width + containerRect.x ||
        top < containerRect.y ||
        containerRect.y > containerRect.y + containerRect.height
      ) {
        ElMessage.error('请把节点拖入到画布中')
        return
      }
      left = left - containerRect.x + _this.refs.efContainer.scrollLeft
      top = top - containerRect.y + _this.refs.efContainer.scrollTop
      // 居中
      left -= 85
      top -= 16
      left = left / scale1
      top = top / scale1
      const nodeId = getUUID()
      // 动态生成名字
      const origName = Menu.name
      const nodeName = origName
      const nodeLabel = getDefaultConditionLabel(Menu.type)
      const node = {
        id: nodeId,
        name: nodeName,
        type: Menu.type,
        left: left + 'px',
        top: top + 'px',
        ico: Menu.ico,
        condition: getDefaultConditionList(Menu.type, nodeId, nodeLabel),
        data: getDefaultNode(Menu.type)
      }
      /**
       * 这里可以进行业务判断、是否能够添加该节点
       */
      ;(state.data.nodeList as NodeObj[]).push(node)
      state.savedNodeList.push(nodeId)
      await nextTick()
      for (let h = 0; h < node.condition.length; h++) {
        state.jsPlumb.makeSource(nodeId + '-' + h, jsplumbSourceOptions)
      }
      state.jsPlumb.makeTarget(nodeId, jsplumbTargetOptions)
      state.jsPlumb.draggable(nodeId, {
        containment: 'parent',
        stop(el: any) {
          // 拖拽节点结束后的对调
          // console.log('拖拽结束: ', el)
        }
      })
    }

    // 获取节点默认信息
    const getDefaultNode = (type: string): NodeDataObj => {
      return defaultNode[type]
    }

    // 加载流程图
    const dataReload = async (data: any) => {
      state.easyFlowVisible = false
      ;(state.data.nodeList as NodeObj[]) = []
      ;(state.data.lineList as LineListObj[]) = []
      await nextTick()
      state.easyFlowVisible = true
      state.data = cloneDeep(data)
      await nextTick()
      state.jsPlumb = jsPlumb.getInstance()
      await nextTick()
      jsPlumbInit()
    }

    const jsPlumbInit = () => {
      state.jsPlumb.ready(() => {
        // 导入默认配置
        state.jsPlumb.importDefaults(jsplumbSetting)
        zoomCanvas()
        // 会使整个jsPlumb立即重绘。
        state.jsPlumb.setSuspendDrawing(false, true)
        // 初始化节点
        loadEasyFlow()
        // 单点击了连接线, https://www.cnblogs.com/ysx215/p/7615677.html
        state.jsPlumb.bind('click', (conn: any) => {
          state.activeElement.type = 'line'
          state.activeElement.sourceId = conn.sourceId
          state.activeElement.targetId = conn.targetId
          const line = state.jsPlumb.getConnections({
            source: conn.sourceId,
            target: conn.targetId
          })[0]
          state.jsPlumb.deleteConnection(line)
          const connParam = {
            source: conn.sourceId,
            target: conn.targetId
            // paintStyle: {
            //   // stroke: 'red',
            //   strokeWidth: 3,
            //   // 设置外边线的颜色，默认设置透明，这样别人就看不见了，点击线的时候可以不用精确点击，参考 https://blog.csdn.net/roymno2/article/details/72717101
            //   outlineStroke: 'transparent',
            //   // 线外边的宽，值越大，线的点击范围越大
            //   outlineWidth: 10
            // },
            // hoverPaintStyle: { stroke: 'red', strokeWidth: 3 }
          }
          state.jsPlumb.connect(connParam, jsplumbConnectOptions)
        })
        // 连线
        state.jsPlumb.bind('connection', (evt: any) => {
          const from = evt.source.id
          const to = evt.target.id
          if (state.loadEasyFlowFinish) {
            ;(state.data.lineList as LineListObj[]).push({ from, to })
          }
        })

        // 删除连线回调
        state.jsPlumb.bind('connectionDetached', (evt: any) => {
          deleteLine(evt.sourceId, evt.targetId)
        })

        // // 改变线的连接节点
        // state.jsPlumb.bind('connectionMoved', (evt: any) => {
        //   changeLine(evt.originalSourceId, evt.originalTargetId)
        // })

        // 连线右击
        state.jsPlumb.bind('contextmenu', (evt: any) => {
          console.log('contextmenu', evt)
        })

        // 连线
        state.jsPlumb.bind('beforeDrop', (evt: any) => {
          const from = evt.sourceId
          const to = evt.targetId
          if (from.split('-')[0] === to) {
            return false
          }
          if (to === 'startNode') {
            ElMessage.error('开始节点不支持连接')
            return false
          }
          if (hasLineFrom(from)) {
            ElMessage.error('条件无法与多个节点关联')
            return false
          }
          if (to.indexOf(from) > -1) {
            ElMessage.error('节点不支持连接自己')
            return false
          }
          if (from === to) {
            ElMessage.error('节点不支持连接自己')
            return false
          }
          if (hasLine(from, to)) {
            ElMessage.error('该关系已存在,不允许重复创建')
            return false
          }
          if (hashOppositeLine(from, to)) {
            ElMessage.error('不支持两个节点之间连线回环')
            return false
          }
          ElMessage.success('连接成功')
          setConnect(from, to)
          const connParam = {
            source: from,
            target: to,
            paintStyle: {
              // 线的颜色
              stroke: '#8D9DB0',
              // 线的粗细，值越大线越粗
              strokeWidth: 1,
              // 设置外边线的颜色，默认设置透明，这样别人就看不见了，点击线的时候可以不用精确点击，参考 https://blog.csdn.net/roymno2/article/details/72717101
              outlineStroke: 'transparent',
              // 线外边的宽，值越大，线的点击范围越大
              outlineWidth: 1
            }
          }
          state.jsPlumb.connect(connParam, jsplumbConnectOptions)
          return false
        })

        // beforeDetach
        state.jsPlumb.bind('beforeDetach', (evt: any) => {
          console.log('beforeDetach', evt)
        })
        state.jsPlumb.setContainer(_this?.refs.efContainer)
      })
    }

    const setConnect = (from: any, to: any) => {
      console.log(from, to)
      const fromNode = from.split('-')[0]
      const index = from.split('-')[1]
      const obj: any = state.data.nodeList?.find((i) => i.id === fromNode)
      obj.condition[index].to = to
    }

    // 加载流程图
    const loadEasyFlow = async () => {
      // 初始化节点
      for (let i = 0; i < (state.data.nodeList as NodeObj[]).length; i++) {
        const node = (state.data.nodeList as NodeObj[])[i]
        // 设置源点，可以拖出线连接其他节点
        if (!isSingleOutletNode(node.type)) {
          for (let h = 0; h < (node.condition as NodeConditionObj[]).length; h++) {
            state.jsPlumb.makeSource(node.id + '-' + h, merge(jsplumbSourceOptions, {}))
          }
        } else {
          state.jsPlumb.makeSource(node.id, merge(jsplumbSourceOptions, {}))
        }
        // // 设置目标点，其他源点拖出的线可以连接该节点
        state.jsPlumb.makeTarget(node.id, jsplumbTargetOptions)
        if (!node.viewOnly) {
          state.jsPlumb.draggable(node.id, {
            containment: 'parent',
            stop(el: any) {
              // 拖拽节点结束后的对调
              console.log('拖拽结束: ', el)
            }
          })
        }
      }
      await nextTick()
      for (let g = 0; g < (state.data.lineList as LineListObj[]).length; g++) {
        const line = (state.data.lineList as LineListObj[])[g]
        const connParam = {
          source: line.from,
          target: line.to,
          label: line.label ? line.label : '',
          connector: line.connector ? line.connector : '',
          anchors: line.anchors ? line.anchors : undefined,
          paintStyle: line.paintStyle ? line.paintStyle : undefined
        }
        state.jsPlumb.connect(connParam, jsplumbConnectOptions)
      }
      // 初始化连线
      await nextTick()
      state.loadEasyFlowFinish = true
    }

    // 改变节点的位置
    const changeNodeSite = (data: any) => {
      for (let i = 0; i < (state.data.nodeList as NodeObj[]).length; i++) {
        const node = (state.data.nodeList as NodeObj[])[i]
        if (node.id === data.nodeId) {
          node.left = data.left
          node.top = data.top
        }
      }
    }

    const dbClickNode = (evt: any) => {
      state.currentNode = evt
      state.isShowDrawer = true
    }

    const nodeClick = (evt: any) => {
      state.activeElement.type = 'node'
      state.activeElement.nodeId = evt.id
    }

    const hasLine = (from: any, to: any) => {
      for (let i = 0; i < (state.data.lineList as LineListObj[]).length; i++) {
        const line = (state.data.lineList as LineListObj[])[i]
        if (line.from === from && line.to === to) {
          return true
        }
      }
      return false
    }

    const hasLineFrom = (from: any) => {
      for (let i = 0; i < (state.data.lineList as LineListObj[]).length; i++) {
        const line = (state.data.lineList as LineListObj[])[i]
        if (line.from === from) {
          return true
        }
      }
      return false
    }

    // 是否含有相反的线
    const hashOppositeLine = (from: any, to: any) => {
      return hasLine(to, from)
    }

    const isSingleOutletNode = (type: any) => {
      return type === 'start'
    }

    const getDefaultConditionList = (type: any, _: any, Label: any) => {
      let conditionList: any[]
      switch (type) {
        case 'ivr-node-transfer':
          conditionList = [
            {
              label: '超时'
            },
            {
              label: '超量'
            },
            {
              label: '无效'
            }
          ]
          break
        case 'ivr-node-date':
          conditionList = [
            {
              label: '默认',
              value: ''
            }
          ]
          break
        case 'ivr-node-voicemail':
          conditionList = []
          break
        case 'ivr-node-callurl':
          conditionList = [{ label: '节点' }, { label: '失败转向' }, { label: '超时转向' }]
          break
        case 'ivr-node-dynamic-route':
          conditionList = [
            { label: '其他', to: '' },
            { label: '分支1', to: '' }
          ]
          break
        case 'ivr-node-collect':
          conditionList = [{ label: '节点' }, { label: '失败' }]
          break
        default:
          conditionList = [{ label: Label, to: '', value: '' }]
      }
      return conditionList
    }

    const getDefaultConditionLabel = (type: any) => {
      let label
      switch (type) {
        case 'ivr-node-menu':
          label = '按键0'
          break
        case 'ivr-node-play':
          label = '下一步'
          break
        case 'startNode':
          label = '下一步'
          break

        // case  'ivr-node-callurl' :
        //   label = '下一步'
        //   break;
        default:
          label = '条件1'
      }
      return label
    }

    // 删除连线
    const deleteLine = (from: any, to: any) => {
      state.data.lineList = (state.data.lineList as LineListObj[]).filter((line) => {
        if (line.from === from && line.to === to) {
          return false
        }
        return true
      })
    }

    const nodeEditSave = async (condition: any, newNodeList: any, originalConditionLength: any, formId: any) => {
      await nextTick()
      let nodeId: string
      if (formId) {
        nodeId = formId
      } else {
        nodeId = condition[0].id.split('-')[0]
      }
      if (state.savedNodeList.indexOf(nodeId) > -1) {
        state.savedNodeList.splice(state.savedNodeList.indexOf(nodeId), 1)
      }
      const allConnections = cloneDeep(state.jsPlumb.getAllConnections())
      allConnections.forEach((con: any) => {
        if (con.sourceId.split('-')[0] === nodeId) {
          state.jsPlumb.deleteConnectionsForElement(con.sourceId)
        }
      })
      if (originalConditionLength > condition.length) {
        const removeNum = originalConditionLength - condition.length
        for (let i = 1; i < removeNum + 1; i++) {
          state.jsPlumb.remove(nodeId + '-' + (originalConditionLength - i))
        }
      }
      // 重置ivr导航数据
      ;(state.data.nodeList as NodeObj[]) = newNodeList
      await nextTick()
      condition.forEach((item: any) => {
        // state.jsPlumb.unmakeSource(item.id)
        if (!state.jsPlumb.isSource(item.id)) {
          state.jsPlumb.makeSource(item.id, jsplumbSourceOptions)
        }
        if (item.to) {
          state.jsPlumb.connect({ source: item.id, target: item.to }, jsplumbConnectOptions)
        }
      })
      state.isShowDrawer = false
    }

    const nodeEditCancel = () => {
      state.currentNode = {}
      state.isShowDrawer = false
    }

    const ivrSaveClick = async (ivrForm: any) => {
      if (checkIsolated()) {
        ElMessage.error('流程中存在孤立节点，无法保存!')
        return false
      }
      if (await checkNodeRequired()) {
        return false
      }
      let type = 'create'
      if (state.data.id) {
        type = 'update'
      }
      const isCreate = type === 'create'
      const cmpMethod = isCreate ? 'POST' : 'PUT'
      const data = { url: '/call/web/call/action/config/ivr', method: cmpMethod, data: { ...state.data, ...ivrForm } }
      IvrModule.IvrCommonAction(data).then((res: any) => {
        if (res.success) {
          ElMessage.success('保存成功!')
          if (isCreate) {
            mcGetCache.$addOneCache('ivrNav', res.data, 'hasObj', 'id')
          } else {
            mcGetCache.$updateCache('ivrNav', res.data, 'id', 'hasObj')
          }
          emit('ivrSaveSuccess', type)
        }
      })
    }

    onBeforeUnmount(() => IvrModule.toggleNav(false))

    // 校验孤立节点
    const checkIsolated = () => {
      let isRelated: any = false
      ;(state.data.nodeList as NodeObj[]).some((item) => {
        let isFrom = false
        let isTo = false
        if ((state.data.lineList as LineListObj[]) && (state.data.lineList as LineListObj[]).length > 0) {
          ;(state.data.lineList as LineListObj[]).some((lineItem) => {
            if (item.id === lineItem.to) {
              isTo = true
            }
            if (item.id === lineItem.from.split('-')[0]) {
              isFrom = true
            }
            if (item.id === 'startNode') {
              return isTo
            } else {
              return isTo && isFrom
            }
          })
          if (!isTo) {
            if (item.id !== 'startNode') {
              isRelated = true
            }
          }
          return isRelated
        } else {
          isRelated = true
          return false
        }
      })
      return isRelated
    }

    // 校验节点必填项
    const checkNodeRequired = () => {
      return new Promise((resolve) => {
        let astrict = false

        ;(state.data.nodeList as NodeObj[])
          .filter((item) => item.type !== 'ivr-node-start')
          .reverse()
          .forEach(async (item) => {
            if (state.savedNodeList.indexOf(item.id) > -1) {
              astrict = true
              dbClickNode(item)
              await nextTick()
              await _this.refs[item.id].form.validateField()
              return resolve(astrict)
            }
          })
        resolve(astrict)
      })
    }

    const deleteNode = (evt: any) => {
      const nodeId = evt.id
      ElMessageBox.confirm('确定要删除' + evt.name + '节点?', '删除', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
        closeOnClickModal: false,
        icon: markRaw(deleteIcon)
      }).then(async () => {
        /**
         * 这里需要进行业务判断，是否可以删除
         */
        if (state.isShowDrawer) {
          state.isShowDrawer = false
        }
        const nodeNextList: string[] = []
        ;(state.data as IvrData).nodeList = (state.data.nodeList as NodeObj[]).filter((node) => {
          if (node.id === nodeId) {
            nodeNextList.push(nodeId)
            if (node.condition && node.condition.length > 0) {
              nodeNextList.push(nodeId)
              node.condition.forEach((item, index) => {
                nodeNextList.push(nodeId + '-' + index)
              })
            }
            return false
          }
          return true
        })
        await nextTick()
        if (nodeNextList.includes(nodeId)) {
          if (state.data.nodeList) {
            state.data.nodeList.forEach((i) => {
              if (i.condition) {
                i.condition.forEach((q) => {
                  if (q.to === nodeId) {
                    q.to = ''
                  }
                })
              }
            })
          }
        }
        nodeNextList.forEach((element) => {
          state.jsPlumb.removeAllEndpoints(element)
        })
      })
      return true
    }

    const getUUID = () => {
      return uuidv4().replace(/-/g, '')
    }

    const keyBoardClick = () => {
      if (state.activeElement.type === 'line') {
        ElMessageBox.confirm('确定删除所点击的线吗?', '删除', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning',
          icon: markRaw(deleteIcon)
        }).then(() => {
          const conn = state.jsPlumb.getConnections({
            source: state.activeElement.sourceId,
            target: state.activeElement.targetId
          })[0]
          state.jsPlumb.deleteConnection(conn)
          state.activeElement = {
            type: '',
            sourceId: '',
            targetId: ''
          }
        })
      }
    }
    const onDragOver = (e: any) => {
      e.preventDefault()
    }

    const onCancel = () => {
      state.isShowDrawer = false
    }

    return {
      ...toRefs(state),
      keyBoardClick,
      getUUID,
      deleteNode,
      checkNodeRequired,
      checkIsolated,
      ivrSaveClick,
      nodeEditCancel,
      nodeEditSave,
      deleteLine,
      getDefaultConditionLabel,
      getDefaultConditionList,
      isSingleOutletNode,
      hashOppositeLine,
      hasLineFrom,
      hasLine,
      nodeClick,
      dbClickNode,
      changeNodeSite,
      loadEasyFlow,
      setConnect,
      jsPlumbInit,
      dataReload,
      getDefaultNode,
      addNode,
      enlargeCanvas,
      zoomScale,
      canvasRightClick,
      getScale,
      pageBack,
      zoomCanvas,
      setCanvasSize,
      nameMap,
      onDragOver,
      onCancel,
      isEmpty
    }
  }
})
</script>

<style lang="stylus" scoped>
/* 抽屉 */
.m7-drawer
  // :deep(.el-drawer.rtl)
  //   border-top-left-radius: 8px;
  //   border-bottom-left-radius: 8px;
  :deep(.el-drawer__header)
    background-color yellow
    font-size: 14px;
    color: rgba(0, 0, 0, 0.88);
    font-weight: 600;
    margin-bottom: 4px;
    padding: 12px 20px;
    box-shadow: 0px 0px 6px 0px rgba(3, 51, 39, 0.2);
  // .el-dialog__close
  //   font-size: 16px;
  //   color: rgba(0, 0, 0, 0.88);
  //   font-weight: 500;
  // .el-drawer__close-btn
  //   padding: 0;
.ef-dot
  background red
*:focus
  outline none
.workflow
  width 100%
  height 100%
  background #F3F5F7
  border-radius 10px
  box-shadow 0px 0px 4px 0px #E0E6F0, 0px 1px 4px 0px #E0E6F0
  border-radius 10px
.drag-box
  position: relative;
  display: flex;
  border: 24px solid #fff;
  box-sizing: border-box;
  border-top: none;
.drag-ul
  margin 0
  padding 0
  width 100px
  background #f7f9fb
.drop
  position relative
  width 100%
  height 100%
  overflow hidden
  outline none !important
  // background-image: linear-gradient(#eee 1px, transparent 0), linear-gradient(90deg, #eee 1px, transparent 0), linear-gradient(#f5f5f5 1px, transparent 0), linear-gradient(90deg, #f5f5f5 1px, transparent 0);
  // background-size: 75px 75px, 75px 75px, 15px 15px, 15px 15px;
  background-image -webkit-radial-gradient(#E0E6F0 2px, rgba(0, 0, 0, 0) 2px)
  background-image radial-gradient(#E0E6F0 2px, rgba(0, 0, 0, 0) 2px)
  background-size 20px 20px
.drop .canvas
  position relative
  height 10000px
  width 10000px
  outline none !important
  top 0px
  left 0px
  background: var(--mc-gray2-color);
.node
  position absolute
  width 120px
  padding 0 5px
  border-width 2px
  border 1px solid #409eff
  border-radius 5px
.right-menu
  position fixed
  left 9999px
  top 9999px
  z-index 100
  padding 0
  margin 0
  background #fff
.right-menu li
  list-style none
  padding 0 10px
  height 30px
  line-height 30px
  font-size 12px
  cursor pointer
.node-name
  max-width 75px
.select-node
  background #00c88d
  color #fff
.connectorHoverClass path
  stroke #00c88d
.node-log
  height 100%
  width 100%
.side-content
  height 100%
  width 100%
.toolbar
  border-bottom none
  height 50px
  line-height 50px
.drag-item
  margin-bottom 10px
  cursor move
  color rgb(27, 28, 35)
  border 1px solid #409eff
  border-radius 5px
  text-align center
  list-style none
.drag-box
  height 100%
  width 100%
.text-align-right
  text-align right
.cursor-crosshair
  cursor crosshair
.btns
  margin-top 20px
  text-align center
.btn-box
  padding 10px
</style>
<style lang="stylus">
.ivr-edit-drawer
  :deep(.el-drawer__header)
    height 50px
    padding 0 20px 0
  :deep(.el-drawer__body)
    flex none
    height calc(100% - 50px)
</style>
